/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.xeerkat.xmpp;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.infoset.xml.DestinationSource;
import org.infoset.xml.Element;
import org.infoset.xml.Infoset;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;

/**
 *
 * @author alex
 */
public class StanzaRouter implements DestinationSource {
   static Logger LOG = Logger.getLogger(StanzaRouter.class.getName());
   public enum ComparisonType {
      StartsWith,
      Equals
   };

   public interface RouteTest {
      boolean matches(Element start);
   }

   public static class NameRouteTest implements RouteTest {
      Name name;
      public NameRouteTest(Name name) {
         this.name = name;
      }

      public boolean matches(Element start) {
         return start.getName().equals(name);
      }
   }

   public static class AttributeRouteTest implements RouteTest {
      String name;
      String value;
      ComparisonType type;
      public AttributeRouteTest(String name,String value,ComparisonType type) {
         this.name = name;
         this.value = value;
         this.type = type;
      }

      public boolean matches(Element start) {
         String startValue = start.getAttributeValue(name);
         switch (type) {
            case StartsWith:
               return startValue==null ? false : startValue.equals(value);
            case Equals:
               return value.equals(startValue);
         }
         return false;
      }
   }

   public class Route {
      RouteTest test;
      ItemDestination sink;
      Route(RouteTest test,ItemDestination sink) {
         this.test = test;
         this.sink = sink;
      }
   }
   List<Route> routes;
   ItemDestination defaultSink;

   public void addDefaultRoute(ItemDestination sink) {
      this.defaultSink = sink;
   }

   public Route addRoute(ItemDestination sink) {
      Route route = new Route(null,sink);
      routes.add(0,route);
      return route;
   }

   public Route addRouteByAttribute(String attName,String attValue,ComparisonType type,ItemDestination sink) {
      Route route = new Route(new AttributeRouteTest(attName,attValue,type),sink);
      routes.add(0,route);
      return route;
   }

   public Route addRouteByName(Name name,ItemDestination sink) {
      Route route = new Route(new NameRouteTest(name),sink);
      routes.add(0,route);
      return route;
   }

   public Route addRoute(RouteTest test,ItemDestination sink) {
      Route route = new Route(test,sink);
      routes.add(0,route);
      return route;
   }

   public void removeRoute(Route route) {
      routes.remove(route);
   }

   public StanzaRouter() {
      this.routes = new ArrayList<Route>();
   }

   public ItemDestination getDestination()
      throws XMLException
   {
      return new ItemDestination() {
         List<ItemDestination> inUse = new ArrayList<ItemDestination>();
         int level = 0;
         boolean fineLog = LOG.isLoggable(Level.FINE);
         public void send(Item item)
            throws XMLException
         {
            switch (item.getType()) {
               case ElementItem:
                  if (level==0) {
                     Element start = (Element)item;
                     for (Route route : routes) {
                        if (route.test!=null) {
                           if (route.test.matches(start)) {
                              inUse.add(route.sink);
                           }
                        } else {
                           inUse.add(route.sink);
                        }
                     }
                     if (inUse.size()==0 && defaultSink!=null) {
                        inUse.add(defaultSink);
                     }
                  }
                  level++;
                  break;
            }
            for (ItemDestination dest : inUse) {
               dest.send(item);
            }
            switch (item.getType()) {
               case ElementEndItem:
                  level--;
                  if (level==0) {
                     inUse.clear();
                  }
            }
         }
      };
   }

   public ItemDestination getDestination(URI baseURI)
      throws XMLException
   {
      return getDestination();
   }

   public ItemConstructor createItemConstructor() {
      return InfosetFactory.getDefaultInfoset().createItemConstructor();
   }

   public Infoset getInfoset() {
      return InfosetFactory.getDefaultInfoset();
   }

}
